﻿#include "Firework.h"

#include <cmath>
#include <iterator>
#include <algorithm>
#include <ranges>
#include <iterator>
#include <iostream>

#include <vsg/nodes/Switch.h>
#include <vsg/maths/transform.h>

#include "DataSingleton.h"
#include "utilities.h"

ColorAndSpeedlist initBombRosebush();
ColorAndSpeedlist initBombCircleAndInBall();
ColorAndSpeedlist initBombCircle();
ColorAndSpeedlist initBombCircles();
ColorAndSpeedlist initBombBall();

ColorAndSpeedlist initBombLittleLines();
ColorAndSpeedlist initBombLinesAndInBall();
ColorAndSpeedlist initBombLines();


void Firework::init()
{
    m_fireworkParticles.resize(m_fireworkParticlesCount);
    initNode();
    initBombFuncs();
}

void Firework::update(const std::chrono::nanoseconds& delt)
{
    if (m_state == FireworkState::Flying)
    {
        double g = 9.8;
        double delt_seconds = delt.count() * 1e-9;
        double dz = z_speed * delt_seconds + 0.5 * -g * pow(delt_seconds, 2.0);

        m_pos.z += dz;

        z_speed += -g * delt_seconds;

        m_matrixTransform->matrix = vsg::translate(m_pos);

        if (z_speed < 0)
        {
            m_state = FireworkState::Blast;
            m_matrixTransform->matrix = vsg::translate(0.f,0.f,0.f);
            m_switch->setAllChildren(false);
            initBombRandom();
        }
    }

    if (m_state == FireworkState::Blast)
    {
        uint32_t particlesDoneCount = 0;
        for (uint32_t i{ 0 }; i < m_activateFireworkParticlesCount; ++i)
        {
            m_fireworkParticles[i].update(delt);
            if (m_fireworkParticles[i].isDone())
            {
                particlesDoneCount++;
            }
        }
        if (particlesDoneCount >= m_activateFireworkParticlesCount * 0.8)
        {
            invalid();
        }
    }
}

vsg::ref_ptr<vsg::Node> Firework::getNode()
{
    auto scene = vsg::Group::create();
    scene->addChild(m_switch);
    for (auto& fireworkParticle : m_fireworkParticles)
    {
        scene->addChild(fireworkParticle.getNode());
    }
    return scene;
}

void Firework::addNewBombs(vsg::dvec3 pos, const std::vector<vsg::dvec3>& speedList, vsg::vec4 color, uint8_t lengthBomb)
{
    if (m_activateFireworkParticlesCount >= m_fireworkParticlesCount)
    {
        return;
    }
    std::uniform_real_distribution<double> dragCoefficientDis(0.9, 0.99);
    double dragCoefficient = dragCoefficientDis(m_gen);
    uint32_t particleIndex = m_activateFireworkParticlesCount;
    for (int i{ 0 }; i < speedList.size() && particleIndex < m_fireworkParticlesCount; ++i)
    {
        m_fireworkParticles[particleIndex].setPos(pos);
        m_fireworkParticles[particleIndex].setSpeed(speedList[i]);
        m_fireworkParticles[particleIndex].setDragCoefficient(dragCoefficient);
        m_fireworkParticles[particleIndex].setTrailLength(lengthBomb);
        m_fireworkParticles[particleIndex].setColor(color);
        m_fireworkParticles[particleIndex].activate();
        particleIndex++;
    }
    setBombsMovePara(m_activateFireworkParticlesCount, particleIndex);
    m_activateFireworkParticlesCount = particleIndex;
}
void Firework::invalid()
{
    m_activateFireworkParticlesCount = 0;
    m_state = FireworkState::Done;
}
void Firework::initNode()
{
    m_switch = vsg::Switch::create();
    m_matrixTransform = vsg::MatrixTransform::create();
    vsg::GeometryInfo geoInfo;
    geoInfo.position = { 0.f, 0.f, 0.f };
    geoInfo.color = {1.0f, 0.0f, 0.0f};
    geoInfo.transform = vsg::scale(1, 1, 5);
    vsg::StateInfo stateInfo;
    m_matrixTransform->addChild(DataSingleton::instance()->m_builder->createBox(geoInfo, stateInfo));
    m_switch->addChild(true, m_matrixTransform);
}

std::vector<vsg::dvec3> getNormalCone(vsg::dvec3 vertexPos, uint8_t circleCount, uint8_t heightCount, std::function<double(double)> f)
{
    std::vector<vsg::dvec3> res;
    vsg::dvec3 normal = vertexPos;
    vsg::dvec3 normalizationNormal = normalize(normal);
    auto [a, b] = getOtherNormal(normalizationNormal);
    double height = length(normal);
    double deltHeight = 1.0 / (heightCount - 1);
    double deltAngle = 360.0f / circleCount;
    double angle = 0.0f;
    double heightNow = 0.0;
    vsg::dvec3 circlePos{0.0, 0.0, 0.0};
    double radius{1.0};
    double newRadius{1.0};
    vsg::dvec3 pointPos;
    for (int i = 0; i < heightCount; ++i)
    {
        for (int j = 0; j < circleCount; ++j)
        {
            double alpha = vsg::radians(angle);
            double cos_alpha = cos(alpha);
            double sin_alpha = sin(alpha);
            pointPos.x = circlePos.x + newRadius * (a.x * cos_alpha + b.x * sin_alpha);
            pointPos.y = circlePos.y + newRadius * (a.y * cos_alpha + b.y * sin_alpha);
            pointPos.z = circlePos.z + newRadius * (a.z * cos_alpha + b.z * sin_alpha);
            angle += deltAngle;
            res.push_back(pointPos);
        }
        heightNow += deltHeight;
        circlePos = normalizationNormal * heightNow * height;
        newRadius = radius - f(heightNow);
    }
    return res;
}

void listMultiValue(std::vector<vsg::dvec3>& list, double value)
{
    for (auto& node : list)
    {
        node *= value;
    }
}

std::function<ColorAndSpeedlist(void)> initBombConeSeries(std::function<double(double)> f)
{
    return [f]() -> ColorAndSpeedlist
    {
        std::normal_distribution<double> speedDis(20, 5);
        double initSpeed = fabs(speedDis(DataSingleton::instance()->m_gen));

        std::uniform_real_distribution<double> heightDis(0.5, 1.5);

        std::uniform_int_distribution<> heightCountDis(10, 20);
        std::uniform_int_distribution<> alphaCountDis(30, 50);

        uint8_t heightCount = heightCountDis(DataSingleton::instance()->m_gen);
        uint8_t circleCount = alphaCountDis(DataSingleton::instance()->m_gen);

        vsg::dvec3 normal = getRandomNormal(60, 90, 0, 360);
        double height = heightDis(DataSingleton::instance()->m_gen);
        vsg::dvec3 vertexPos = normal * height;
        std::vector<vsg::dvec3> conePointList = getNormalCone(vertexPos, circleCount, heightCount, f);
        listMultiValue(conePointList, initSpeed);
        vsg::vec4 color = getRandomColor();
        ColorAndSpeedlist res;
        res[color] = conePointList;
        return res;
    };
}

void Firework::initBombFuncs()
{
    m_initBombFuncs.push_back(initBombRosebush);
    m_initBombFuncs.push_back(initBombCircleAndInBall);
    m_initBombFuncs.push_back(initBombCircle);
    m_initBombFuncs.push_back(initBombCircles);
    m_initBombFuncs.push_back(initBombBall);

    m_initBombFuncs.push_back(initBombLittleLines);
    m_initBombFuncs.push_back(initBombLinesAndInBall);
    m_initBombFuncs.push_back(initBombLines);
    m_initBombFuncs.push_back(initBombConeSeries([](double x)
    {
        return x;
    }));
    m_initBombFuncs.push_back(initBombConeSeries([](double x)
    {
        return pow(x, 2.0);
    }));

    m_initBombFuncs.push_back(initBombConeSeries([](double x)
    {
        return sqrt(x);
    }));
    m_initBombFuncs.push_back(initBombConeSeries([](double x)
    {
        return (abs(1.0 - sqrt(abs(1.0 - pow(x, 2.0)))));
    }));
}
void Firework::setBombsMovePara(int begin, int end)
{
    std::uniform_real_distribution<double> thetaLDis{ 1.8, 2.5 };
    std::uniform_real_distribution<double> ADis{ 1.0, 1.5 };

    double theta = thetaLDis(m_gen);
    double A = ADis(m_gen);

    for (int i = begin; i < end; ++i)
    {
        m_fireworkParticles[i].setMovePara(theta, A);
    }
}

void Firework::reset(vsg::dvec3 pos, double speed)
{
    m_pos = pos;
    z_speed = speed;
}

void Firework::activate()
{
    m_state = FireworkState::Flying;
    m_matrixTransform->matrix = vsg::scale(1., 1., 1.);
    m_switch->setAllChildren(true);
}

std::vector<vsg::dvec3> getNormalCircle(vsg::dvec3 normal, uint8_t pointCount)
{
    std::vector<vsg::dvec3> res{ pointCount };
    auto [a, b] = getOtherNormal(normal);
    double deltAngle = 360.0 / pointCount;
    double alphaDeg = 0.0;
    vsg::dvec3 pointPos;
    double alpha;
    for (auto i : std::views::iota(0) | std::views::take(pointCount))
    {
        alpha = vsg::radians(alphaDeg);
        pointPos.x = a.x * cos(alpha) + b.x * sin(alpha);
        pointPos.y = a.y * cos(alpha) + b.y * sin(alpha);
        pointPos.z = a.z * cos(alpha) + b.z * sin(alpha);
        alphaDeg += deltAngle;
        res[i] = pointPos;
    }
    return res;
}

std::vector<vsg::dvec3> getNormalBall(uint8_t alphaCount, uint8_t betaCount)
{
    double alpha = 90;
    double deltAlpha = 180. / alphaCount;
    double beta = 0.;
    double deltBeta = 360.0 / betaCount;
    std::vector<vsg::dvec3> res;
    for (int i = 0; i <= alphaCount; ++i)
    {
        for (int j = 0; j < betaCount; ++j)
        {

            vsg::dvec3 relatePos = Polar3DToXYZ(
            {
                1.0, alpha, beta
            });
            res.push_back(relatePos);

            beta += deltBeta;
        }
        alpha -= deltAlpha;
    }
    return res;
}



std::vector<vsg::dvec3> getNormalRosebush(vsg::dvec3 normal, uint8_t bushCount)
{
    auto [normalX, normalY] = getOtherNormal(normal);
    vsg::dmat3 modelCoordinateAxisBase =
    {
        normalX.x, normalY.x, normal.x,
        normalX.y, normalY.y, normal.y,
        normalX.z, normalY.z, normal.z
    };

    // r = cos(a*theta);
    // x = r*cos(theta)
    // y = r*sin(theta)
    // z = a/2*sin(a*theta) Or z = r*sin(a*theta) Or z = 0
    int pointCount = 200 * bushCount;
    double deltTheta = 360. / pointCount;
    double theta = 0;
    std::vector<vsg::dvec3> res(pointCount);
    vsg::dvec3 pointPos;
    double thetaRad;
    for (auto i : std::views::iota(0) | std::views::take(pointCount))
    {
        thetaRad = vsg::radians(theta);
        theta += deltTheta;
        double r = cos(bushCount * thetaRad);
        pointPos = { r * cos(thetaRad), r * sin(thetaRad), 0 };
        pointPos = modelCoordinateAxisBase * pointPos;
        res[i] = pointPos;
    }

    return res;
}

ColorAndSpeedlist initBombRosebush()
{
    std::uniform_real_distribution<double> radiusDis(10, 40);
    double radius = radiusDis(DataSingleton::instance()->m_gen);
    vsg::dvec3 normal = getRandomNormal(-90, 90, 0, 360);
    std::uniform_int_distribution<> bushCountDis(2, 30);
    std::vector<vsg::dvec3> rosebushNormalPos = getNormalRosebush(normal, bushCountDis(DataSingleton::instance()->m_gen));
    listMultiValue(rosebushNormalPos, radius);
    vsg::vec4 color = getRandomColor();
    ColorAndSpeedlist res;
    res[color] = rosebushNormalPos;
    return res;
}

ColorAndSpeedlist initBombCircleAndInBall()
{
    std::uniform_int_distribution circlePointCountDis(70, 100);
    std::uniform_int_distribution circleCountDis(3, 8);
    uint8_t circleCount = circleCountDis(DataSingleton::instance()->m_gen);

    std::normal_distribution<double> speedDis(20, 5);
    double initSpeed = fabs(speedDis(DataSingleton::instance()->m_gen));

    std::vector<vsg::dvec3> speedList;
    for (int i = 0; i < circleCount; ++i)
    {
        vsg::dvec3 normal = getRandomNormal(-90, 90, 0, 360);
        std::vector<vsg::dvec3> circlePointPos = getNormalCircle(normal, circlePointCountDis(DataSingleton::instance()->m_gen));
        listMultiValue(circlePointPos, initSpeed);
        std::copy_n(circlePointPos.begin(), circlePointPos.size(), std::back_inserter(speedList));
    }
    vsg::vec4 color = getRandomColor();
    ColorAndSpeedlist res;
    res[color] = speedList;

    std::uniform_int_distribution<> alphaCountDis(10, 20);
    int alphaCountRandom = alphaCountDis(DataSingleton::instance()->m_gen);
    std::uniform_int_distribution<> betaCountDis(10, 20);
    int betaCountRandom = betaCountDis(DataSingleton::instance()->m_gen);
    double speedRatio = 0.7;
    double iterRatio = 0.6;
    while (isToDo(iterRatio))
    {
        color = getRandomColor();
        initSpeed = initSpeed * speedRatio;
        std::vector<vsg::dvec3> ballPointPosList = getNormalBall(alphaCountRandom, betaCountRandom);
        listMultiValue(ballPointPosList, initSpeed);
        res[color] = ballPointPosList;
    }
    return res;
}

ColorAndSpeedlist initBombCircle()
{
    std::normal_distribution<double> speedDis(10, 1);
    double initSpeed = fabs(20 + speedDis(DataSingleton::instance()->m_gen));

    std::uniform_int_distribution pointCountDis(800, 1500);
    vsg::dvec3 normal = getRandomNormal(-90, 90, 0, 360);
    std::vector<vsg::dvec3> circlePointPos = getNormalCircle(normal, pointCountDis(DataSingleton::instance()->m_gen));
    listMultiValue(circlePointPos, initSpeed);
    vsg::vec4 color = getRandomColor();
    ColorAndSpeedlist res;
    res[color] = circlePointPos;
    return res;
}

ColorAndSpeedlist initBombCircles()
{
    std::normal_distribution<double> speedDis(5, 0.1);
    double initSpeed = fabs(speedDis(DataSingleton::instance()->m_gen));

    std::uniform_int_distribution pointCountDis(10, 50);

    vsg::dvec3 normal = getRandomNormal(-90, 90, 0, 360);
    std::vector<vsg::dvec3> speedlist = getNormalCircle(normal, 30 + pointCountDis(DataSingleton::instance()->m_gen));
    listMultiValue(speedlist, initSpeed);
    vsg::vec4 color = getRandomColor();

    double speedRatio = 1.3;
    double iterRatio = 0.1;
    double iterCount = 0;
    while (isToDo(iterRatio) || iterCount < 4)
    {
        iterCount++;
        initSpeed *= speedRatio;
        std::vector<vsg::dvec3> circlePointPos = getNormalCircle(normal, (iterCount + 1) * pointCountDis(DataSingleton::instance()->m_gen));
        listMultiValue(circlePointPos, initSpeed);
        std::copy_n(circlePointPos.begin(), circlePointPos.size(), std::back_inserter(speedlist));

        if (iterCount > 8)
            break;
    }
    color = getRandomColor();
    ColorAndSpeedlist res;
    res[color] = speedlist;
    return res;
}

ColorAndSpeedlist initBombBall()
{
    std::normal_distribution<double> speedDis(20, 5);
    double initSpeed = fabs(speedDis(DataSingleton::instance()->m_gen));

    std::uniform_int_distribution<> alphaCountDis(10, 30);
    int alphaCountRandom = alphaCountDis(DataSingleton::instance()->m_gen);
    std::uniform_int_distribution<> betaCountDis(10, 30);
    int betaCountRandom = betaCountDis(DataSingleton::instance()->m_gen);
    std::vector<vsg::dvec3> ballPointPosList = getNormalBall(alphaCountRandom, betaCountRandom);
    listMultiValue(ballPointPosList, initSpeed);
    vsg::vec4 color = getRandomColor();
    ColorAndSpeedlist res;
    res[color] = ballPointPosList;

    double speedRatio = 0.8;
    while (isToDo(0.5))
    {
        initSpeed = initSpeed * speedRatio;
        alphaCountRandom = alphaCountDis(DataSingleton::instance()->m_gen);
        betaCountRandom = betaCountDis(DataSingleton::instance()->m_gen);
        ballPointPosList = getNormalBall(alphaCountRandom, betaCountRandom);
        listMultiValue(ballPointPosList, initSpeed);
        color = getRandomColor();
        ColorAndSpeedlist res;
        res[color] = ballPointPosList;
    }
    return res;
}

ColorAndSpeedlist initBombLittleLines()
{
    std::normal_distribution<double> speedDis(25, 5);
    std::normal_distribution<double> speedLittleDis(0, 0.5);
    double initSpeed = fabs(speedDis(DataSingleton::instance()->m_gen));

    std::uniform_int_distribution<> lineCountDis(865, 1085);

    uint8_t lineCount = lineCountDis(DataSingleton::instance()->m_gen);
    std::vector<vsg::dvec3> speedList;
    for (int i = 0; i < lineCount; ++i)
    {
        double tmpSpeed = fabs(initSpeed + speedLittleDis(DataSingleton::instance()->m_gen));
        vsg::dvec3 normal = getRandomNormal(30, 90, 0, 360);
        vsg::dvec3 speed = normal * tmpSpeed;
        speedList.push_back(speed);
    }
    auto color = getRandomColor();
    ColorAndSpeedlist res;
    res[color] = speedList;
    return res;
}

ColorAndSpeedlist initBombLinesAndInBall()
{
    std::normal_distribution<double> speedDis(25, 5);
    double initSpeed = fabs(speedDis(DataSingleton::instance()->m_gen));
    std::normal_distribution<double> speedLittleDis(0, 0.5);

    std::uniform_int_distribution<> lineCountDis(300, 500);

    uint8_t lineCount = lineCountDis(DataSingleton::instance()->m_gen);
    std::vector<vsg::dvec3> speedList;
    for (int i = 0; i < lineCount; ++i)
    {
        double tmpSpeed = fabs(initSpeed + speedLittleDis(DataSingleton::instance()->m_gen));
        vsg::dvec3 normal = getRandomNormal(-90, 90, 0, 360);
        vsg::dvec3 speed = normal * tmpSpeed;
        speedList.push_back(speed);
    }
    vsg::vec4 color = getRandomColor();
    ColorAndSpeedlist res;
    res[color] = speedList;

    std::uniform_int_distribution<> alphaCountDis(10, 20);
    int alphaCountRandom = alphaCountDis(DataSingleton::instance()->m_gen);
    std::uniform_int_distribution<> betaCountDis(10, 20);
    int betaCountRandom = betaCountDis(DataSingleton::instance()->m_gen);
    double speedRatio = 0.8;
    double iterRatio = 0.4;
    while (isToDo(iterRatio))
    {
        initSpeed = initSpeed * speedRatio;
        std::vector<vsg::dvec3> ballPointPosList = getNormalBall(alphaCountRandom, betaCountRandom);
        listMultiValue(ballPointPosList, initSpeed);
        color = getRandomColor();
        res[color] = ballPointPosList;
        alphaCountRandom = alphaCountDis(DataSingleton::instance()->m_gen);
        betaCountRandom = betaCountDis(DataSingleton::instance()->m_gen);
    }
    return res;
}

ColorAndSpeedlist initBombLines()
{
    std::normal_distribution<double> speedDis(25, 3);
    double initSpeed = fabs(speedDis(DataSingleton::instance()->m_gen));
    std::normal_distribution<double> speedLittleDis(0, 0.5);

    std::uniform_int_distribution<> lineCountDis(100, 200);

    uint8_t lineCount = lineCountDis(DataSingleton::instance()->m_gen);
    std::vector<vsg::dvec3> speedList;
    for (int i = 0; i < lineCount; ++i)
    {
        double tmpSpeed = fabs(initSpeed + speedLittleDis(DataSingleton::instance()->m_gen));
        vsg::dvec3 normal = getRandomNormal(-90, 90, 0, 360);
        vsg::dvec3 speed = normal * tmpSpeed;
        speedList.push_back(speed);
    }
    vsg::vec4 color = getRandomColor();
    ColorAndSpeedlist res;
    res[color] = speedList;

    double speedRatio = 0.8;
    double iterRatio = 0.5;
    double iterCount = 0;
    while (isToDo(iterRatio))
    {
        iterCount++;
        speedList.clear();

        color = getRandomColor();
        lineCount = lineCountDis(DataSingleton::instance()->m_gen);
        initSpeed = initSpeed * speedRatio;
        for (int i = 0; i < lineCount; ++i)
        {
            double speedRandom = fabs(initSpeed + speedLittleDis(DataSingleton::instance()->m_gen) * pow(speedRatio, iterCount));
            vsg::dvec3 normal = getRandomNormal(-90, 90, 0, 360);
            vsg::dvec3 speed = normal * speedRandom;
            speedList.push_back(speed);
        }
        res[color] = speedList;
    }
    return res;
}

void Firework::initBombRandom()
{
    int classCount = (int)m_initBombFuncs.size() - 1;
    std::uniform_int_distribution<> bombFuncDis(0, classCount);
    int funcIndex = bombFuncDis(m_gen);
    ColorAndSpeedlist res = m_initBombFuncs[funcIndex]();
    std::uniform_int_distribution<> trailLengthDis(2, 6);
    int trailLength = trailLengthDis(m_gen);
    for (auto& node : res)
    {
        addNewBombs(m_pos, node.second, node.first, trailLength);
    }
}
